package foxesandrabbits.species;

import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Random;

import foxesandrabbits.field.Field;
import foxesandrabbits.field.Location;
import foxesandrabbits.random.Randomizer;

/**
 * A class representing shared characteristics of alive objects.
 * 
 * @author Baptiste Viale and Hadrien Luttiau
 */
public abstract class AliveObject
{
	// Whether the animal is alive or not.
	protected boolean alive;
	// The animal's field.
	private Field field;
	// The animal's position in the field.
	private Location location;
	// The animal's age.
	protected int age;
	// The likelihood of a fox breeding.
	protected double breedingProba ;
	// The age at which a fox can start to breed.
	protected static int BREEDING_AGE = 8;
	// The age to which an AliveObject can live except vegetables.
	protected static int MAX_AGE = 100;
	// The maximum number of births.
	protected static int MAX_LITTER_SIZE = 2;
	// A shared random number generator to control breeding.
	protected static Random rand = Randomizer.getRandom();

	/**
	 * Create a new animal at location in field.
	 * 
	 * @param field The field currently occupied.
	 * @param location The location within the field.
	 */
	public AliveObject(boolean randomAge, Field field, Location location, double breedingP)
	{
		breedingProba=breedingP;
		this.alive = true;
		this.field = field;
		setLocation(location);

		if(randomAge)
			age = rand.nextInt(MAX_AGE);
		else
			age = 0;
	}

	/**
	 * Increase the age. This could result in the fox's death.
	 */
	private void incrementAge()
	{
		age++;
		if(age > MAX_AGE) {
			//System.out.println("This " + this.getClass().getName() + " is too old to live anymore ! (" + age + " yo)");
			setDead();
		}
	}
	/**
	 * A fox can breed if it has reached the breeding age.
	 */
	private boolean canBreed()
	{
		return age >= BREEDING_AGE;
	}

	/**
	 * Generate a number representing the number of births,
	 * if it can breed.
	 * @return The number of births (may be zero).
	 */
	private int breed()
	{
		int births = 0;
		if(canBreed() && rand.nextDouble() <= breedingProba)
		{
			births = rand.nextInt(MAX_LITTER_SIZE) + 1;
		}
		return births;
	}

	/**
	 * Check whether or not this fox is to give birth at this step.
	 * New births will be made into free adjacent locations.
	 * @param newAnimals A list to return newly born animals.
	 */
	protected void giveBirth(List<AliveObject> newAnimals)
	{
		// New animals are born into adjacent locations.
		// Get a list of adjacent free locations.
		Field field = getField();
		List<Location> free = field.getFreeAdjacentLocations(getLocation());
		int births = breed();
		for(int b = 0; b < births && free.size() > 0; b++) {
			Location loc = free.remove(0);
			AliveObject young = null;
			try {
				young = (AliveObject)this.getClass().getConstructors()[0].newInstance(false, field, loc);
				//System.out.println("A baby " + this.getClass().getName() + " is born !");
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
			newAnimals.add(young);
		}
	}

	/**
	 * Make this animal act - that is: make it do
	 * whatever it wants/needs to do.
	 * @param newAnimals A list to receive newly born animals.
	 */
	public void act(List<AliveObject> newAnimals)
	{
		incrementAge();
		if(isAlive()) {
			giveBirth(newAnimals);            
		}
	}

	/**
	 * Check whether the animal is alive or not.
	 * @return true if the animal is still alive.
	 */
	public boolean isAlive()
	{
		return alive;
	}

	protected void setAlive() {
		this.alive = true;
	}

	/**
	 * Indicate that the animal is no longer alive.
	 * It is removed from the field.
	 */
	protected void setDead()
	{
		alive = false;
		if(location != null) {
			field.clear(location);
			location = null;
			field = null;
		}
	}

	/**
	 * Return the animal's location.
	 * @return The animal's location.
	 */
	public Location getLocation()
	{
		return location;
	}

	/**
	 * Place the animal at the new location in the given field.
	 * @param newLocation The animal's new location.
	 */
	protected void setLocation(Location newLocation)
	{
		if(location != null) {
			field.clear(location);
		}
		location = newLocation;
		field.place(this, newLocation);
	}
	
	protected void setMyBalls(Location newLocation)
	{
		location = newLocation;
		field.place(this, newLocation);
	}
	
	/**
	 * Return the animal's field.
	 * @return The animal's field.
	 */
	protected Field getField()
	{
		return field;
	}

	protected void setField(Field field) {
		this.field = field;
	}

	public int getAge() {
		return age;
	}
}
